﻿using System;
using System.Collections.Generic;
using System.Globalization;

namespace System
{
	public static class Fluent
	{
		public static TO Throws<TO>(this TO monad, string exception)
			where TO : class
		{
			if (monad != null) return monad;

			throw new ArgumentNullException(exception);
		}

		public static TO Throws<TO>(this TO monad, string exception, params object[] args)
			where TO : class
		{
			if (monad != null) return monad;

			throw new ArgumentNullException(String.Format(CultureInfo.CurrentCulture, exception, args));
		}

		public static IEnumerable<TR> ToEnumerable<TO, TR>(this TO monad, Func<TO, int> fromFunc, Func<TO, int> toFunc, Func<TO, int, TR> iterationFunc)
			where TO : class
			where TR : class
		{
			if (monad == null) yield break;

			var from = fromFunc(monad);
			var to = toFunc(monad);

			for (var i = from; i <= to; ++i)
			{
				var val = iterationFunc(monad, i);
				if (val != null) yield return val;
			}
		}

		public static TO Is<TO>(this TO monad, Func<TO, bool> condition)
			where TO : class
		{
			if (monad == null) return null;

			return condition(monad) ? monad : null;
		}

		public static void Do<TO>(this TO monad, Action<TO> action)
			where TO : class
		{
			if (monad != null && action != null)
				action(monad);
		}

		public static TR If<TO, TR>(this TO monad, Func<TO, bool> condition, Func<TO, TR> trueFunc, Func<TO, TR> falseFunc)
			where TO : class
			where TR : class
		{
			if (trueFunc == null) throw new ArgumentNullException("trueFunc");
			if (falseFunc == null) throw new ArgumentNullException("falseFunc");

			if (monad == null) return null;

			return condition(monad) ? trueFunc(monad) : falseFunc(monad);
		}

		public static TR If<TO, TR>(this TO monad, Func<TO, bool> condition, Func<TO, TR> trueFunc, TR falseResult)
			where TO : class
			where TR : class
		{
			if (trueFunc == null) throw new ArgumentNullException("trueFunc");
			if (monad == null) return null;

			return condition(monad) ? trueFunc(monad) : falseResult;
		}

		public static TO If<TO>(this TO monad, Func<TO, bool> condition)
			where TO : class
		{
			if (monad == null) return null;

			return condition(monad) ? monad : null;
		}

		public static TR ThrowIf<TR>(this TR monad, Func<TR, bool> func, string exception)
			where TR : class
		{
			if (func == null) throw new ArgumentNullException("func");

			var ret = func(monad);
			if (ret) throw new ArgumentNullException(exception);
			return monad;
		}

		public static TR With<TO, TR>(this TO monad, Func<TO, TR> func)
			where TO : class
			where TR : class
		{
			if (func == null) throw new ArgumentNullException("func");

			return monad != null ? func(monad) : null;
		}

		public static TR With<TO, TR>(this TO monad, TR result)
			where TO : class
			where TR : class
		{
			return monad != null ? result : null;
		}

		public static TR Return<TO, TR>(this TO monad, Func<TO, TR> func, TR defaultValue)
			where TO : class
			where TR : class
		{
			if (func == null) throw new ArgumentNullException("func");

			return monad != null ? func(monad) : defaultValue;
		}

		public static TR As<TR>(this object monad)
			where TR : class
		{
			return monad as TR;
		}

		public static ExceptionOrObject<TR> Try<TO, TR>(this TO monad, Func<TO, TR> func)
			where TO : class
			where TR : class
		{
			if (func == null) throw new ArgumentNullException("func");

			TR res;
			Exception ex = null;

			try
			{
				res = func(monad);
			}
			catch (Exception e)
			{
				res = null;
				ex = e;
			}

			return new ExceptionOrObject<TR>(res, ex);
		}

		public static Exception Try<TO>(this TO monad, Action<TO> action)
			where TO : class
		{
			if (action == null) throw new ArgumentNullException("action");

			Exception ex = null;

			try
			{
				action(monad);
			}
			catch (Exception e)
			{
				ex = e;
			}

			return ex;
		}

		public static TR Catch<TR>(this ExceptionOrObject<TR> monad, Func<Exception, TR> func)
			where TR : class
		{
			if (monad == null) return null;
			
			if (monad.Exception != null && func == null) return null;

			return monad.Exception != null ? func(monad.Exception) : monad.Result;
		}

		public static void Catch<TEx>(this TEx exception, Action<TEx> action)
			where TEx : Exception
		{
			if (exception == null) return;

			if (action != null)
				action(exception);
		}
	}

	public class ExceptionOrObject<TR>
		where TR : class
	{
		public TR Result { get; set; }
		public Exception Exception { get; set; }

		public ExceptionOrObject(TR obj, Exception ex)
		{
			this.Result = obj;
			this.Exception = ex;
		}
	}
}
